//xml file i/o
//$Id: xml_file.h 372 2010-03-14 18:12:07Z Oleg.Bulychov $
#pragma once

#include <fstream>

//"MIT license is an academic license"
#define RAPIDXML_NO_EXCEPTIONS
#include "contrib/rapidxml/rapidxml.hpp"
#include "contrib/rapidxml/rapidxml_print.hpp"

#include "src/heo/include/reflection.h"
#include "src/heo/include/ref_ptr.h"
#include "src/heo/include/ptr_visit.h"
#include "src/heo/include/factory.h"

inline void rapidxml::parse_error_handler(const char* what, void* where)
{
    std::cout << "XML parse error: '" << what << "'" << std::endl;
    std::abort();
}

//__________________________________________________________________________________
namespace xml
{

class file
{
    DISALLOW_COPY_AND_ASSIGN(file);
public:
    std::vector<char> buffer_;
    rapidxml::xml_document<> doc_;

    explicit file()
    {}

    template<class V>
    void accept(V& v)
    {
        v(VAR_(buffer));
    }

    bool load(char const* szFile) 
    {
        std::ifstream in(szFile);
        if (!in)
        {
            std::cout << "ERROR: '" << szFile << "' not found" << std::endl;
            return false;
        }
        in.seekg(0, std::ios::end);
        std::streamsize length = in.tellg();
        in.seekg(0, std::ios::beg);

        buffer_.resize(length + 1);
        if (length <= 0)
        {
            std::cout << "ERROR: '" << szFile << "' is empty" << std::endl;
            return false;
        }
        in.read(&buffer_[0], length);
        return true;
    }

    void parse()
    {
        if (buffer_.size() > 1)
            doc_.parse<0/*parse_declaration_node*/>(&buffer_[0]);
    }

    bool save(char const* szFile)
    {
        std::ofstream out(szFile);
        out << doc_;
        return true;
    }
};
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
class in:
    public meta::visitor<in>
{
public:
    rapidxml::xml_node<>* node_;
public:
    explicit in(file const& f, char const* szRoot = "root"):
        node_(f.doc_.first_node(szRoot))
    {}

    explicit in(in& parent, char const* szName):
        node_(NULL)
    {
        ASSERT_(*szName);
        if (parent.node_)
        {
            if ('/' == *szName && !*(szName + 1))
                node_ = parent.node_;
            else
                node_ = parent.node_->first_node(szName);
        }        
    }

    explicit in(rapidxml::xml_node<>* node):
        node_(node)
    {}

    bool is_Null() const { return NULL == node_; }

    template<class T>
    in& operator >>(T& t)
    {
        visit_T(t, T::get_Name());
        return *this;
    }

    template<typename T, typename D>
    void visit_Class(T& t, char const* szName, D const&)
    {
        if (*szName)
        {
            in inner(*this, szName);
            t.accept(inner);
        }
        else
        {
            in inner(*this, T::get_Name());
            t.accept(inner);
        }
    }

    template<typename T, typename D>
    void visit(T& t, char const* szName, D const& d)
    {
        ASSERT_(*szName);
        if (node_)
        {
            if ('/' == *szName)
            {
                ++szName;
                if (*szName)
                {
                    rapidxml::xml_node<>* it = node_ ? node_->first_node(szName) : NULL;
                    if (it)
                    {
                        char* value = it->value();
                        meta::From_String(t, szName, value, d);
                        return;
                    }
                }
                else
                {
                    char* value = node_->value();
                    meta::From_String(t, szName, value, d);
                    return;
                }
            }
            else
            {
                rapidxml::xml_attribute<>* attr = node_->first_attribute(szName);
                if (attr)
                {
                    char* value = attr->value();
                    meta::From_String(t, szName, value, d);
                    return;
                }
            }
        }
        meta::Set_Default(t, d);
    }

    template<typename T, typename D>
    void visit_Array(T* pt, size_t N, char const* szName, D const& d)
    {
        ASSERT_(*szName);
        rapidxml::xml_node<>* parent = node_ ? node_->first_node(szName) : NULL;
        rapidxml::xml_node<>* it = parent ? parent->first_node() : NULL;
        for (size_t i = 0; i < N; ++i)
        {
            in inner(it);
            inner(pt[i], "/", d);
            if (it)
                it = it->next_sibling();
        }
    }

    template<typename T, typename D>
    void visit_Ptr(T* pt, char const* szName, D const& d)
    {}
};
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
class out:
    public meta::visitor<out>
{
    DISALLOW_COPY_AND_ASSIGN(out);
public:
    rapidxml::xml_document<>& doc_;
    rapidxml::xml_node<>* node_;
public:
    explicit out(file& f,
        char const* szRoot = "root",
        char const* szVersion = "1.0",
        char const* szEncoding = "UTF-8",
        char const* szStandalone = NULL):
        doc_(f.doc_)
    {
        node_ = doc_.first_node(szRoot);
        if (!node_)
        {
            rapidxml::xml_node<>* header = doc_.first_node();
            if (!header || header->type() != rapidxml::node_declaration)
            {
                header = doc_.allocate_node(rapidxml::node_declaration);
                doc_.insert_node(NULL, header);
                if (szVersion)
                {
                    rapidxml::xml_attribute<>* attr = doc_.allocate_attribute("version",
                        szVersion);
                    header->append_attribute(attr);
                }
                if (szEncoding)
                {
                    rapidxml::xml_attribute<>* attr = doc_.allocate_attribute("encoding",
                        szEncoding);
                    header->append_attribute(attr);
                }
                if (szStandalone)
                {
                    rapidxml::xml_attribute<>* attr = doc_.allocate_attribute("standalone",
                        szStandalone);
                    header->append_attribute(attr);
                }
            }
            node_ = doc_.allocate_node(rapidxml::node_element, szRoot);
            doc_.append_node(node_);
        }
    }

    explicit out(out& parent, char const* szName):
        doc_(parent.doc_)
    {
        if ('/' == *szName && !*(szName + 1))
        {
            node_ = parent.node_;
        }
        else
        {
            node_ = doc_.allocate_node(rapidxml::node_element, doc_.allocate_string(szName));
            parent.node_->insert_node(NULL, node_);
        }
    }

    explicit out(out& parent, rapidxml::xml_node<>* node):
        doc_(parent.doc_), node_(node)
    {}

    template<class T>
    void operator <<(T& t)
    {
        visit_T(t, T::get_Name());
    }

    template<typename T, typename D>
    void visit_Class(T& t, char const* szName, D const&)
    {
        if (*szName)
        {
            out inner(*this, szName);
            t.accept(inner);
        }
        else
        {
            out inner(*this, T::get_Name());
            t.accept(inner);
        }
    }

    template<typename T, typename D>
    void visit(T& t, char const* szName, D const& d)
    {
        ASSERT_(node_);
        if (!*szName)
        {
            out inner(*this, "item");//TODO: use type name like "int", "float"!?
            inner(t, "/", d);
            return;
        }
        std::string s;
        char const* str = meta::Value2String(t, s);
        if ('/' == *szName)
        {
            ++szName;
            if (*szName)
            {
                rapidxml::xml_node<>* it = doc_.allocate_node(rapidxml::node_element, doc_.allocate_string(szName));
                it->value(doc_.allocate_string(str));
                node_->append_node(it);
            }
            else
            {
                ASSERT_(!*node_->value());
                node_->value(doc_.allocate_string(str)); 
            }
            return;
        }
        rapidxml::xml_attribute<>* attr = doc_.allocate_attribute(doc_.allocate_string(szName),
            doc_.allocate_string(str));
        node_->append_attribute(attr);
    }

    template<typename T, typename D>
    void visit_Array(T* pt, size_t N, char const* szName, D const& d)
    {
        rapidxml::xml_node<>* parent = doc_.allocate_node(rapidxml::node_element, szName);
        node_->insert_node(NULL, parent);
        for (size_t i = 0; i < N; ++i)
        {
            out inner(*this, parent);
            inner(pt[i], "", d);//:don't know how to name, yet
        }
    }

    template<typename T, typename D>
    void visit_Ptr(T* pt, char const* szName, D const& d)
    {}
};

}//xml
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
//__________________________________________________________________________________
namespace meta
{

template<class X>
struct reflection_handler<std::vector<X>, xml::in>
{
    template<class D>
    static void visit(std::vector<X>& vec, char const* szName, D const& d, xml::in& v)
    {
        ASSERT_(*szName);
        if (v.node_)
        {
            rapidxml::xml_node<>* parent = v.node_->first_node(szName);
            if (parent)
            {
                size_t i = 0;
                for (rapidxml::xml_node<>* it = parent->first_node(); it; it = it->next_sibling())
                {
                    ++i;
                }
                vec.clear();
                if (i > 0)
                {
                    vec.resize(i);
                    i = 0;
                    for (rapidxml::xml_node<>* it = parent->first_node(); it; it = it->next_sibling())
                    {
                        xml::in inner(it);
                        inner(vec[i++], "/");
                    }
                }
                return;
            }
        }
        Set_Default(vec, d);
    }
};
//__________________________________________________________________________________
template<class X>
struct reflection_handler<std::vector<X>, xml::out>
{
    template<class D>
    static void visit(std::vector<X>& vec, char const* szName, D const&, xml::out& v)
    {
        ASSERT_(*szName);
        rapidxml::xml_node<>* parent = v.doc_.allocate_node(rapidxml::node_element, szName);
        v.node_->insert_node(NULL, parent);
        for (size_t i = 0; i < vec.size(); ++i)
        {
            xml::out inner(v, parent);
            inner(vec[i], "");//:don't know how to name, yet
        }
    }
};

//TODO where it should be?
template<class T>
struct reflection_handler< ref_ptr<T>, xml::in>
{
    template<class D>
    static void visit(ref_ptr<T>& pt, char const* szName, D const& d, xml::in& v)
    {
        ASSERT_(*szName);
        if (v.node_)
        {
            if ('/' == *szName && !*(szName + 1))
            {
                char const* obj_name = v.node_->name();
                if (!ascii_strcmpi(obj_name, "null"))
                {
                    pt.clear();
                    return;
                }
                ref_ptr<T> p;
                p.attach(factory<T>::instance().create_Object(obj_name));
                if (p)
                {
                    xml::in inner(v.node_);
                    Dispatch_Visit(inner, p.in(), "/");

                    pt.attach(p.detach());
                    return;
                }
                else
                {//TODO review: where it should be?
#if defined(LOG_BAD_DATA)
#if (1 == LOG_BAD_DATA)
                    std::cout << "WARNING: invalid object name: '" << obj_name << "'" << std::endl;
#endif
#endif
                }
            }
            else
            {
                rapidxml::xml_node<>* parent = v.node_->first_node(szName);
                if (parent)
                {
                    rapidxml::xml_node<>* it = parent->first_node();
                    xml::in inner(it);
                    inner(pt, "/", d);
                    return;
                }
            }
        }
        Set_Default(pt, d);
    }
};

template<class T>
struct reflection_handler< ref_ptr<T>, xml::out>
{
    template<class D>
    static void visit(ref_ptr<T>& pt, char const* szName, D const& d, xml::out& v)
    {
        if (*szName)
        {
            xml::out inner(v, szName);
            inner(pt, "", d);//:don't know how to name, yet
            return;
        }
        if(pt)
        {
            Dispatch_Visit(v, pt.in(), "");//:don't know how to name, yet
        }
        else
        {
            rapidxml::xml_node<>* it = v.doc_.allocate_node(rapidxml::node_element, "null");
            v.node_->insert_node(NULL, it);
        }
    }
};

}//meta
//__________________________________________________________________________________
REGISTER_VISITOR(xml::in);
REGISTER_VISITOR(xml::out);

//EOF!
